using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using log4net;
using System.Reflection;
using System.Configuration;
namespace AnguaRPGEngine.Library.Sockets
{
    /// <summary>
    /// represents a UDP datagram; derived from BaseSocket
    /// </summary>
    public class DataGramClass : BaseSocket
    {
        private static readonly log4net.ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private Socket receiveSocket;

        /// <summary>
        /// the remoteEndPoint for the datagram
        /// </summary>
        public string endPoint = "";
        /// <summary>
        /// used to notify owner when new data is received
        /// </summary>
        /// <param name="dg">the DataGramClass object</param>
        /// <param name="data">the newly received data</param>
        public delegate void ReceiveEventHandler(DataGramClass dg, string data);
        /// <summary>
        /// raised when new data is received
        /// </summary>
        public event ReceiveEventHandler OnReceive;

        StateObject state = null;

        IPEndPoint localEndPoint;

        /// <summary>
        /// constructs new object with specified buffer size
        /// </summary>
        /// <param name="bufferSize">specifies the size of the receive buffer</param>
        public DataGramClass(int bufferSize)
        {
            receiveBufferSize = bufferSize;
        }

        /// <summary>
        /// constructs new object with default buffer size of 10240
        /// </summary>
        public DataGramClass()
            : this(10240)
        {
            state = new StateObject(receiveBufferSize);
        }

        /// <summary>
        /// binds the socket to the specified port
        /// </summary>
        /// <param name="port">port to bind to</param>
        /// <returns>0 if success, less than 0 if failure</returns>
        public int Start(int port)
        {
            int rc = 0;
            try
            {
                localEndPoint = new IPEndPoint(IPAddress.Any, port);
                receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                receiveSocket.Bind(localEndPoint);
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
                rc = -1;
            }
            catch (Exception e)
            {
                rc = -1;
            }
            return rc;
        }

        /// <summary>
        /// begins to asynchronously receive data 
        /// </summary>
        public void StartReceiveFrom()
        {
            try
            {
                state.RemoteEP = new IPEndPoint(IPAddress.Any, 7101);
                state.WorkSocket = receiveSocket;
                EndPoint ep = state.RemoteEP;
                receiveSocket.BeginReceiveFrom(state.Buffer, 0, state.BufferSize, 0,
                    ref ep, new AsyncCallback(ReceiveFromCallBack), state);
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
            }
        }

        /// <summary>
        /// called when new data is received; raises the OnReceive event to the owner
        /// </summary>
        /// <param name="ar">the IAsyncResult object that holds the receive buffer</param>
        public void ReceiveFromCallBack(IAsyncResult ar)
        {
            try
            {
                StateObject state = (StateObject)ar.AsyncState;
                EndPoint ep = state.RemoteEP;
                int bytesRead = state.WorkSocket.EndReceiveFrom(ar, ref ep);

                if (bytesRead > 0)
                {
                    endPoint = ep.ToString();
                    OnReceive(this, Encoding.Default.GetString(state.Buffer, 0, bytesRead));
                    StartReceiveFrom();
                }
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
            }
        }

        /// <summary>
        /// closes the socket
        /// </summary>
        public void Close()
        {
            try
            {
                if (receiveSocket != null)
                    receiveSocket.Close();
            }
            catch { }
        }


        

        /// <summary>
        /// Sends the specified data to the host over the port.  You must first use Start() to bind to a host port.
        /// If Start is not used prior to SendTo() a default host port will be assigned.
        /// </summary>
        /// <param name="host">Hostname or IP of remote.</param>
        /// <param name="port">Port to send data to</param>
        /// <param name="data">Datagram payload</param>
        /// <returns>Port number of sending Socket LocalEndPoint or -1 on error.</returns>
        public int SendTo(string host, int port, string data)
        {
            Log.Debug("SendTo() Entry");
            IPAddress ipAddress;
            IPEndPoint RemoteEP;

            int rc = 0;

            try
            {
                byte[] byteData = Encoding.Default.GetBytes(data);
                ipAddress = GetIPAddress(host);
                if (ipAddress == null) return -1;
                RemoteEP = new IPEndPoint(ipAddress, port);
                //EndPoint localEndPoint = new IPEndPoint(ipAddress, port);

                if (this.receiveSocket == null)
                {
                    this.receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                }

                rc = this.receiveSocket.SendTo(byteData, 0, data.Length, 0, RemoteEP);
                rc = ((IPEndPoint)this.receiveSocket.LocalEndPoint).Port;
                RaiseOnSend(rc);

                //SendSock.Close();
            }
            catch (SocketException se)
            {
                Log.Error("SendTo() SocketException", se);
                RaiseOnSocketError(se);
                rc = -1;
            }
            catch (Exception e)
            {
                Log.Error("SendTo() Exception", e);
                rc = -1;
            }
            return rc;
        }
    }
}
